#!/bin/sh

# Copyright 2011-2018 the Pacemaker project contributors
#
# The version control history for this file may have further details.
# 
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
# 
# This software is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
# 
# You should have received a copy of the GNU General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#

# cibsecret
#
# Manage the secrets directory (by default, /var/lib/pacemaker/lrm/secrets).
# Secrets are ASCII files, holding one value per file:
# <secrets-directory>/<rsc>/<param>

. @OCF_ROOT_DIR@/lib/heartbeat/ocf-shellfuncs

LRM_CIBSECRETS=@LRM_CIBSECRETS_DIR@

PROG=`basename $0`
SSH_OPTS="-o StrictHostKeyChecking=no"

usage() {
	echo "cibsecret - A tool for managing cib secrets";
	echo "";
	echo "usage: $PROG [-C] <command> <parameters>";
	echo "--version              Display version information, then exit";
	echo "";
	echo "-C: don't read/write the CIB"
	echo ""
	echo "command: set | delete | stash | unstash | get | check | sync"
	echo ""
	echo "	set <rsc> <param> <value>"
	echo ""
	echo "	get <rsc> <param>"
	echo ""
	echo "	check <rsc> <param>"
	echo ""
	echo "	stash <rsc> <param> (if not -C)"
	echo ""
	echo "	unstash <rsc> <param> (if not -C)"
	echo ""
	echo "	delete <rsc> <param>"
	echo ""
	echo "	sync"
	echo ""
	echo "stash/unstash: move the parameter from/to the CIB (if you already"
	echo "have the parameter set in the CIB)."
	echo ""
	echo "set/delete: add/remove a parameter from the local file."
	echo ""
	echo "get: display the parameter from the local file."
	echo ""
	echo "check: verify MD5 hash of the parameter from the local file and the CIB."
	echo ""
	echo "sync: copy $LRM_CIBSECRETS to other nodes."
	echo ""
	echo "Examples:"
	echo ""
	echo "	$PROG set ipmi_node1 passwd SecreT_PASS"
	echo ""
	echo "	$PROG stash ipmi_node1 passwd"
	echo ""
	echo "	$PROG get ipmi_node1 passwd"
	echo ""
	echo "	$PROG check ipmi_node1 passwd"
	echo ""
	echo "	$PROG sync"

	exit $1
}
fatal() {
	echo "ERROR: $*"
	exit 1
}
warn() {
	echo "WARNING: $*"
}
info() {
	echo "INFO: $*"
}

check_env() {
	which md5sum >/dev/null 2>&1 ||
		fatal "please install md5sum to run $PROG"
	if which pssh >/dev/null 2>&1; then
		rsh=pssh_fun
		rcp=pscp_fun
	elif which pdsh >/dev/null 2>&1; then
		rsh=pdsh_fun
		rcp=pdcp_fun
	elif which ssh >/dev/null 2>&1; then
		rsh=ssh_fun
		rcp=scp_fun
	else
		fatal "please install pssh, pdsh, or ssh to run $PROG"
	fi
	ps -ef | grep '[p]acemaker-controld' >/dev/null ||
		fatal "pacemaker not running? $PROG needs pacemaker"
}

get_other_nodes() {
	crm_node -l | awk '{print $2}' | grep -v `uname -n`
}

get_live_nodes() {
	if [ `id -u` = 0 ] && which fping >/dev/null 2>&1; then
		fping -a $@ 2>/dev/null
	else
		local h
		for h; do ping -c 2 -q $h >/dev/null 2>&1 && echo $h; done
	fi
}

check_down_nodes() {
	local n down_nodes
	down_nodes=`(for n; do echo $n; done) | sort | uniq -u`
	if [ -n "$down_nodes" ]; then
		if [ `echo $down_nodes | wc -w` = 1 ]; then
			warn "node $down_nodes is down"
			warn "you'll need to update it using $PROG sync later"
		else
			warn "nodes `echo $down_nodes` are down"
			warn "you'll need to update them using $PROG sync later"
		fi
	fi
}

pssh_fun() {
	pssh -qi -H "$nodes" -x "$SSH_OPTS" $*
}
pscp_fun() {
	pscp -q -H "$nodes" -x "-pr" -x "$SSH_OPTS" $*
}
pdsh_fun() {
	local pdsh_nodes=`echo $nodes | tr ' ' ','`
	export PDSH_SSH_ARGS_APPEND="$SSH_OPTS"
	pdsh -w $pdsh_nodes $*
}
pdcp_fun() {
	local pdsh_nodes=`echo $nodes | tr ' ' ','`
	export PDSH_SSH_ARGS_APPEND="$SSH_OPTS"
	pdcp -pr -w $pdsh_nodes $*
}
ssh_fun() {
	local h
	for h in $nodes; do
		ssh $SSH_OPTS $h $* || return
	done
}
scp_fun() {
	local h src="$1" dest=$2
	for h in $nodes; do
		scp -pr -q $SSH_OPTS $src $h:$dest || return
	done
}
# TODO: this procedure should be replaced with csync2
# provided that csync2 has already been configured
sync_files() {
	local crm_nodes=`get_other_nodes`
	local nodes=`get_live_nodes $crm_nodes`
	check_down_nodes $nodes $crm_nodes
	[ "$nodes" = "" ] && {
		info "no other nodes live"
		return
	}
	info "syncing $LRM_CIBSECRETS to `echo $nodes` ..."
	$rsh rm -rf $LRM_CIBSECRETS &&
		$rsh mkdir -p `dirname $LRM_CIBSECRETS` &&
		$rcp $LRM_CIBSECRETS `dirname $LRM_CIBSECRETS`
}
sync_one() {
	local f=$1 f_all="$1 $1.sign"
	local crm_nodes=`get_other_nodes`
	local nodes=`get_live_nodes $crm_nodes`
	check_down_nodes $nodes $crm_nodes
	[ "$nodes" = "" ] && {
		info "no other nodes live"
		return
	}
	info "syncing $f to `echo $nodes` ..."
	$rsh mkdir -p `dirname $f` &&
		if [ -f "$f" ]; then
			$rcp "$f_all" `dirname $f`
		else
			$rsh rm -f $f_all
		fi
}

is_secret() {
	# assume that the secret is in the CIB if we cannot talk to
	# cib
	[ "$NO_CRM" ] ||
	test "$1" = "$MAGIC"
}
check_cib_rsc() {
	local rsc=$1 output
	output=`$NO_CRM crm_resource -r $rsc -W >/dev/null 2>&1` ||
		fatal "resource $rsc doesn't exist: $output"
}
get_cib_param() {
	local rsc=$1 param=$2
	check_cib_rsc $rsc
	$NO_CRM crm_resource -r $rsc -g $param 2>/dev/null
}
set_cib_param() {
	local rsc=$1 param=$2 value=$3
	check_cib_rsc $rsc
	$NO_CRM crm_resource -r $rsc -p $param -v "$value" 2>/dev/null
}
remove_cib_param() {
	local rsc=$1 param=$2
	check_cib_rsc $rsc
	$NO_CRM crm_resource -r $rsc -d $param 2>/dev/null
}

localfiles() {
	local cmd=$1
	local rsc=$2 param=$3 value=$4
	local local_file=$LRM_CIBSECRETS/$rsc/$param
	case $cmd in
	"get")
		cat $local_file 2>/dev/null
		true
		;;
	"getsum")
		cat $local_file.sign 2>/dev/null
		true
		;;
	"set")
		local md5sum
		md5sum=`printf $value | md5sum` ||
			fatal "md5sum failed to produce hash for resource $rsc parameter $param"
		md5sum=`echo $md5sum | awk '{print $1}'`
		mkdir -p `dirname $local_file` &&
			echo $value > $local_file &&
			echo $md5sum > $local_file.sign &&
			sync_one $local_file
		;;
	"remove")
		rm -f $local_file
		rm -f $local_file.sign
		sync_one $local_file
	;;
	*)
		# not reached, this is local interface
	;;
	esac
}
get_local_param() {
	local rsc=$1 param=$2
	localfiles get $rsc $param
}
set_local_param() {
	local rsc=$1 param=$2 value=$3
	localfiles set $rsc $param $value
}
remove_local_param() {
	local rsc=$1 param=$2
	localfiles remove $rsc $param
}

cibsecret_set() {
	local value=$1

	if [ -z "$NO_CRM" ]; then
		[ "$current" -a "$current" != "$MAGIC" -a "$current" != "$value" ] &&
			fatal "CIB value <$current> different for $rsc parameter $param; please delete it first"
	fi
	set_local_param $rsc $param $value &&
	set_cib_param $rsc $param "$MAGIC"
}

cibsecret_check() {
	local md5sum local_md5sum
	is_secret "$current" ||
		fatal "resource $rsc parameter $param not set as secret, nothing to check"
	local_md5sum=`localfiles getsum $rsc $param`
	[ "$local_md5sum" ] ||
		fatal "no MD5 hash for resource $rsc parameter $param"
	md5sum=`printf "$current_local" | md5sum | awk '{print $1}'`
	[ "$md5sum" = "$local_md5sum" ] ||
		fatal "MD5 hash mismatch for resource $rsc parameter $param"
}

cibsecret_get() {
	cibsecret_check
	echo "$current_local"
}

cibsecret_delete() {
	remove_local_param $rsc $param &&
	remove_cib_param $rsc $param
}

cibsecret_stash() {
	[ "$NO_CRM" ] &&
		fatal "no access to Pacemaker, stash not supported"
	[ "$current" = "" ] &&
		fatal "nothing to stash for resource $rsc parameter $param"
	is_secret "$current" &&
		fatal "resource $rsc parameter $param already set as secret, nothing to stash"
	cibsecret_set "$current"
}

cibsecret_unstash() {
	[ "$NO_CRM" ] &&
		fatal "no access to Pacemaker, unstash not supported"
	[ "$current_local" = "" ] &&
		fatal "nothing to unstash for resource $rsc parameter $param"
	is_secret "$current" ||
		warn "resource $rsc parameter $param not set as secret, but we have local value so proceeding anyway"
	remove_local_param $rsc $param &&
	set_cib_param $rsc $param $current_local
}

cibsecret_sync() {
	sync_files
}

MAGIC="lrm://"
umask 0077

if [ "$1" = "-C" ]; then
	NO_CRM=':'
	shift 1
fi

cmd=$1
rsc=$2
param=$3
value=$4

case "$cmd" in
	set) [ $# -ne 4 ] && usage 1;;
	get) [ $# -ne 3 ] && usage 1;;
	check) [ $# -ne 3 ] && usage 1;;
	stash) [ $# -ne 3 ] && usage 1;;
	unstash) [ $# -ne 3 ] && usage 1;;
	delete) [ $# -ne 3 ] && usage 1;;
	sync) [ $# -ne 1 ] && usage 1;;
	--help) usage 0;;
	--version)
		crm_attribute --version
		exit $?;;
	*) usage 1;
esac

check_env

# we'll need these two often
current=`get_cib_param $rsc $param`
current_local=`get_local_param $rsc $param`

cibsecret_$cmd $value
